/**
 * Copyright (c) 2019-2029, wwww.kiwipeach.cn (liuburu@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package open.kiwipeach.autoconfigure.shiro.realam;

import cn.kiwipeach.blog.domain.BlogRoleTpye;
import cn.kiwipeach.blog.database.domain.PermissionVO;
import cn.kiwipeach.blog.database.domain.RolePermissionVO;
import cn.kiwipeach.blog.enums.BlogSys;
import cn.kiwipeach.blog.exception.BlogException;
import cn.kiwipeach.blog.database.generator.domain.RUserRole;
import cn.kiwipeach.blog.database.generator.domain.SysFunction;
import cn.kiwipeach.blog.database.generator.domain.SysUser;
import cn.kiwipeach.blog.database.generator.mapper.RUserRoleMapper;
import cn.kiwipeach.blog.database.generator.mapper.SysUserMapper;
import cn.kiwipeach.blog.database.mapper.CustomSysFunctionMapper;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import open.kiwipeach.autoconfigure.shiro.token.AccessToken;
import open.kiwipeach.autoconfigure.web.utils.JwtTokenUtil;
import open.kiwipeach.autoconfigure.web.utils.OauthUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import javax.security.auth.login.AccountLockedException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 博客自定义认证授权Realm
 *
 * @author kiwipeach
 * @since 1.0
 */
@Slf4j
public class CustomShiroRealm extends AuthorizingRealm {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private RUserRoleMapper userRoleMapper;

    @Autowired
    private CustomSysFunctionMapper customSysFunctionMapper;


    public CustomShiroRealm(CredentialsMatcher credentialsMatcher) {
        //需要配置token的类型，否则会报错
        setAuthenticationTokenClass(AccessToken.class);
        super.setCredentialsMatcher(credentialsMatcher);
    }

    /**
     * 用户认证
     *
     * @param authenticationToken 代认证的token
     * @return 返回带有token和密文的认证信息（密码比对就是比对该token中的用户密码和密文是否匹配）
     * @throws AuthenticationException 认证异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        AccessToken targetToken = (AccessToken) authenticationToken;
        //TODO 从数据库中查找到了一条用户
        String platform = targetToken.getPlatform();
        AccessToken dbAccessToken = null;
        if (BlogSys.SYSTEM.toString().equalsIgnoreCase(platform)) {
            Wrapper<SysUser> queryWrapper = new QueryWrapper<SysUser>().eq("USER_NAME", targetToken.getUserName()).eq("PLATFORM", platform);
            SysUser sysUser = sysUserMapper.selectOne(queryWrapper);
            try {
                userExceptionHandle(sysUser);
            } catch (AccountLockedException e) {
                throw new BlogException("-lOGIN_001", e.getMessage());
            }
            dbAccessToken = changeSysUser2AccessToken(sysUser);
        } else {
            dbAccessToken = targetToken;
        }
        String password = dbAccessToken.getPassword();
        dbAccessToken.setPassword(null);
        // 2）准备角色权限信息
        QueryWrapper<RUserRole> queryWrapper = new QueryWrapper<RUserRole>().eq("USER_ID", dbAccessToken.getId());
        List<RUserRole> sysRoles = userRoleMapper.selectList(queryWrapper);
        if (sysRoles.size() > 0) {
            // 当前用户存在角色信息才加载角色权限信息
            List<RolePermissionVO> rolePermissionVOS = customSysFunctionMapper.selectRolePermisssion(dbAccessToken.getId());
            List<SysFunction> sysFunctions = customSysFunctionMapper.selectList(new QueryWrapper<>());
            for (RolePermissionVO role : rolePermissionVOS) {
                if (BlogRoleTpye.USER.equals(role.getRoleName())) {
                    sysFunctions = sysFunctions.stream().filter(f -> "system".equals(f.getModule())).collect(Collectors.toList());
                    List<PermissionVO> permissions = JSON.parseArray(JSON.toJSONString(sysFunctions), PermissionVO.class);
                    role.setPermissions(permissions);
                } else if (BlogRoleTpye.ADMIN.equals(role.getRoleName())) {
                    List<PermissionVO> permissions = JSON.parseArray(JSON.toJSONString(sysFunctions), PermissionVO.class);
                    role.setPermissions(permissions);
                }
            }
            dbAccessToken.setRolePermissions(rolePermissionVOS);
        }
        // 用户token签发
        JwtTokenUtil.signToken(dbAccessToken);
        AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(dbAccessToken, password, "CustomShiroRealm");
        //密码比对器：cn.kiwipeach.web.shiro.credential.BlogCredentialsMatcher
        return authenticationInfo;
    }

    private void userExceptionHandle(SysUser sysUser) throws AccountLockedException {
        if (sysUser == null) {
            throw new UnknownAccountException("用户不存在!");
        } else {
            String lockStatus = "1";
            if (lockStatus.equals(sysUser.getAccountLock())) {
                throw new AccountLockedException("锁定状态码信息（请联系管理员处理）：" + sysUser.getLockReason());
            }
        }
    }

    /**
     * 父类信息转换到子类信息
     *
     * @param sysUser 系统用户
     * @return 返回用户accessToken
     */
    private AccessToken changeSysUser2AccessToken(SysUser sysUser) {
        AccessToken accessToken = new AccessToken();
        accessToken.setPlatform("system");
        accessToken.setId(sysUser.getId());
        accessToken.setUserName(sysUser.getUserName());
        //密码不显示出去
        accessToken.setPassword(sysUser.getPassword());
        accessToken.setThirdUserId(sysUser.getThirdUserId());
        accessToken.setNickName(sysUser.getNickName());
        accessToken.setEmail(sysUser.getEmail());
        accessToken.setHeadUrl(sysUser.getHeadUrl());
        accessToken.setAccountLock(sysUser.getAccountLock());
        accessToken.setCreateTime(sysUser.getCreateTime());
        accessToken.setUpdateTime(sysUser.getUpdateTime());
        accessToken.setLockReason(sysUser.getLockReason());
        accessToken.setToken(OauthUtil.encryptAccessToken(sysUser.getUserName()));
        return accessToken;
    }

    /**
     * 用户授权（//TODO 猜想：在有权限注解或者标签的时候会去执行该方法，判断当前用户是否具有访问权限）
     * 1、subject.hasRole(“admin”)
     * 2、@RequiresRoles("admin")
     * 3、在页面上加shiro标签的时候
     *
     * @param principalCollection 认证对象
     * @return 返回权限集合
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        AccessToken accessToken = (AccessToken) principalCollection.getPrimaryPrincipal();
        // 0）访问需要授权的资源时，需要验证用户的token合法性
        if (!JwtTokenUtil.verify(accessToken.getToken())) {
            throw new BlogException("-LOGIN-002", "当前用户token失效或非法，禁止访问");
        }
        // 1）查询当前用户的完整信息
        String decorateUserName = accessToken.getUserName();
        Wrapper<SysUser> userQuery = new QueryWrapper<SysUser>().eq("USER_NAME", decorateUserName);
        SysUser sysUser = sysUserMapper.selectOne(userQuery);
        if (sysUser == null) {
            // 没有任何权限
            return new SimpleAuthorizationInfo();
        }

        // 2）准备角色权限信息
        List<RolePermissionVO> roles = customSysFunctionMapper.selectRolePermisssion(sysUser.getId());

        // 3）封装返回包装的用户角色权限信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = wrapperAuthorizationInfo(roles);

        return simpleAuthorizationInfo;
        //无权限抛出AuthorizationException异常，可以在全局异常处理中对该异常进行判断，跳转到未授权页面
    }

    private SimpleAuthorizationInfo wrapperAuthorizationInfo(List<RolePermissionVO> roles) {
        if (roles.size() == 1 && roles.get(0) == null) {
            return new SimpleAuthorizationInfo();
        }
        Set<String> roleSet = new HashSet<String>();
        Set<String> permissionsSet = new HashSet<String>();
        for (RolePermissionVO role : roles) {
            roleSet.add(role.getRoleName());
            List<PermissionVO> permissions = role.getPermissions();
            for (PermissionVO permission : permissions) {
                permissionsSet.add(permission.getCode());
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleSet);
        info.addStringPermissions(permissionsSet);
        return info;
    }


}
